// The following touch screen support code by maxpautsch was merged 1/10/17
// https://github.com/maxpautsch

// Define TOUCH_CS is the user setup file to enable this code

// A demo is provided in examples Generic folder

// Additions by Bodmer to double sample, use Z value to improve detection reliability
// and to correct rotation handling

// See license in root directory.

/***************************************************************************************
** Function name:           begin_touch_read_write - was spi_begin_touch
** Description:             Start transaction and select touch controller
***************************************************************************************/
// The touch controller has a low SPI clock rate
inline void TFT_eSPI::begin_touch_read_write(void) {
  DMA_BUSY_CHECK;
  CS_H;  // Just in case it has been left low
#if defined(SPI_HAS_TRANSACTION) && defined(SUPPORT_TRANSACTIONS)
  if (locked) {
    locked = false;
    spi.beginTransaction(SPISettings(SPI_TOUCH_FREQUENCY, MSBFIRST, SPI_MODE0));
  }
#else
  spi.setFrequency(SPI_TOUCH_FREQUENCY);
#endif
  SET_BUS_READ_MODE;
  T_CS_L;
}

/***************************************************************************************
** Function name:           end_touch_read_write - was spi_end_touch
** Description:             End transaction and deselect touch controller
***************************************************************************************/
inline void TFT_eSPI::end_touch_read_write(void) {
  T_CS_H;
#if defined(SPI_HAS_TRANSACTION) && defined(SUPPORT_TRANSACTIONS)
  if (!inTransaction) {
    if (!locked) {
      locked = true;
      spi.endTransaction();
    }
  }
#else
  spi.setFrequency(SPI_FREQUENCY);
#endif
  SET_BUS_WRITE_MODE;
}

/***************************************************************************************
** Function name:           Legacy - deprecated
** Description:             Start/end transaction
***************************************************************************************/
void TFT_eSPI::spi_begin_touch() {
  begin_touch_read_write();
}
void TFT_eSPI::spi_end_touch() {
  end_touch_read_write();
}
void TFT_eSPI::set_touch_rotation(uint8_t rotation) {
  _rotation = rotation;
}
/***************************************************************************************
** Function name:           getTouchRaw
** Description:             read raw touch position.  Always returns true.
***************************************************************************************/
uint8_t TFT_eSPI::getTouchRaw(uint16_t* x, uint16_t* y) {
  uint16_t tmp1, tmp2;

  begin_touch_read_write();

  // Start YP sample request for x position, read 4 times and keep last sample
  spi.transfer(0xd0);  // Start new YP conversion
  spi.transfer(0);     // Read first 8 bits
  spi.transfer(0xd0);  // Read last 8 bits and start new YP conversion
  spi.transfer(0);     // Read first 8 bits
  spi.transfer(0xd0);  // Read last 8 bits and start new YP conversion
  spi.transfer(0);     // Read first 8 bits
  spi.transfer(0xd0);  // Read last 8 bits and start new YP conversion

  tmp1 = spi.transfer(0);  // Read first 8 bits
  tmp1 = tmp1 << 5;
  tmp1 |= 0x1f & (spi.transfer(0x90) >> 3);  // Read last 8 bits and start new XP conversion

  //*x = tmp;

  // Start XP sample request for y position, read 4 times and keep last sample
  spi.transfer(0);     // Read first 8 bits
  spi.transfer(0x90);  // Read last 8 bits and start new XP conversion
  spi.transfer(0);     // Read first 8 bits
  spi.transfer(0x90);  // Read last 8 bits and start new XP conversion
  spi.transfer(0);     // Read first 8 bits
  spi.transfer(0x90);  // Read last 8 bits and start new XP conversion

  tmp2 = spi.transfer(0);  // Read first 8 bits
  tmp2 = tmp2 << 5;
  tmp2 |= 0x1f & (spi.transfer(0) >> 3);  // Read last 8 bits

  //*y = tmp;

  switch (_rotation) {
    case 0:
      *x = 4095 - tmp2;
      *y = tmp1;
      break;
    case 1:
      *x = tmp1;
      *y = tmp2;
      break;
    case 2:
      *x = tmp2;
      *y = 4095 - tmp1;
      break;
    default:  // 3
      *x = 4095 - tmp1;
      *y = 4095 - tmp2;
  }
  end_touch_read_write();
  return true;
}

/***************************************************************************************
** Function name:           getTouchRawZ
** Description:             read raw pressure on touchpad and return Z value.
***************************************************************************************/
uint16_t TFT_eSPI::getTouchRawZ(void) {
  begin_touch_read_write();

  // Z sample request
  int16_t tz = 0xFFF;
  spi.transfer(0xb0);               // Start new Z1 conversion
  tz += spi.transfer16(0xc0) >> 3;  // Read Z1 and start Z2 conversion
  tz -= spi.transfer16(0x00) >> 3;  // Read Z2

  end_touch_read_write();

  return (uint16_t) tz;
}

/***************************************************************************************
** Function name:           validTouch
** Description:             read validated position. Return false if not pressed.
***************************************************************************************/
#define _RAWERR 20  // Deadband error allowed in successive position samples
uint8_t TFT_eSPI::validTouch(uint16_t* x, uint16_t* y, uint16_t threshold) {
  uint16_t x_tmp, y_tmp, x_tmp2, y_tmp2;

  // Wait until pressure stops increasing to debounce pressure
  uint16_t z1 = 1;
  uint16_t z2 = 0;
  while (z1 > z2) {
    z2 = z1;
    z1 = getTouchRawZ();
    delay(1);
  }

  //  Serial.print("Z = ");Serial.println(z1);

  if (z1 <= threshold)
    return false;

  getTouchRaw(&x_tmp, &y_tmp);

  //  Serial.print("Sample 1 x,y = "); Serial.print(x_tmp);Serial.print(",");Serial.print(y_tmp);
  //  Serial.print(", Z = ");Serial.println(z1);

  delay(1);  // Small delay to the next sample
  if (getTouchRawZ() <= threshold)
    return false;

  delay(2);  // Small delay to the next sample
  getTouchRaw(&x_tmp2, &y_tmp2);

  //  Serial.print("Sample 2 x,y = "); Serial.print(x_tmp2);Serial.print(",");Serial.println(y_tmp2);
  //  Serial.print("Sample difference = ");Serial.print(abs(x_tmp - x_tmp2));Serial.print(",");Serial.println(abs(y_tmp - y_tmp2));

  if (abs(x_tmp - x_tmp2) > _RAWERR)
    return false;
  if (abs(y_tmp - y_tmp2) > _RAWERR)
    return false;

  *x = x_tmp;
  *y = y_tmp;

  return true;
}

/***************************************************************************************
** Function name:           getTouch
** Description:             read callibrated position. Return false if not pressed.
***************************************************************************************/
#define Z_THRESHOLD 350  // Touch pressure threshold for validating touches
uint8_t TFT_eSPI::getTouch(uint16_t* x, uint16_t* y, uint16_t threshold) {
  uint16_t x_tmp, y_tmp;

  if (threshold < 20)
    threshold = 20;
  if (_pressTime > millis())
    threshold = 20;

  uint8_t n = 5;
  uint8_t valid = 0;
  while (n--) {
    if (validTouch(&x_tmp, &y_tmp, threshold))
      valid++;
    ;
  }

  if (valid < 1) {
    _pressTime = 0;
    return false;
  }

  _pressTime = millis() + 50;

  convertRawXY(&x_tmp, &y_tmp);

  if (x_tmp >= _width || y_tmp >= _height)
    return false;

  _pressX = x_tmp;
  _pressY = y_tmp;
  *x = _pressX;
  *y = _pressY;
  return valid;
}

/***************************************************************************************
** Function name:           convertRawXY
** Description:             convert raw touch x,y values to screen coordinates
***************************************************************************************/
void TFT_eSPI::convertRawXY(uint16_t* x, uint16_t* y) {
  uint16_t x_tmp = *x, y_tmp = *y, xx, yy;

  if (!touchCalibration_rotate) {
    xx = (x_tmp - touchCalibration_x0) * _width / touchCalibration_x1;
    yy = (y_tmp - touchCalibration_y0) * _height / touchCalibration_y1;
    if (touchCalibration_invert_x)
      xx = _width - xx;
    if (touchCalibration_invert_y)
      yy = _height - yy;
  } else {
    xx = (y_tmp - touchCalibration_x0) * _width / touchCalibration_x1;
    yy = (x_tmp - touchCalibration_y0) * _height / touchCalibration_y1;
    if (touchCalibration_invert_x)
      xx = _width - xx;
    if (touchCalibration_invert_y)
      yy = _height - yy;
  }
  *x = xx;
  *y = yy;
}

/***************************************************************************************
** Function name:           calibrateTouch
** Description:             generates calibration parameters for touchscreen.
***************************************************************************************/
void TFT_eSPI::calibrateTouch(uint16_t* parameters, uint32_t color_fg, uint32_t color_bg, uint8_t size) {
  int16_t values[] = {0, 0, 0, 0, 0, 0, 0, 0};
  uint16_t x_tmp, y_tmp;

  for (uint8_t i = 0; i < 4; i++) {
    fillRect(0, 0, size + 1, size + 1, color_bg);
    fillRect(0, _height - size - 1, size + 1, size + 1, color_bg);
    fillRect(_width - size - 1, 0, size + 1, size + 1, color_bg);
    fillRect(_width - size - 1, _height - size - 1, size + 1, size + 1, color_bg);

    if (i == 5)
      break;  // used to clear the arrows

    switch (i) {
      case 0:  // up left
        drawLine(0, 0, 0, size, color_fg);
        drawLine(0, 0, size, 0, color_fg);
        drawLine(0, 0, size, size, color_fg);
        break;
      case 1:  // bot left
        drawLine(0, _height - size - 1, 0, _height - 1, color_fg);
        drawLine(0, _height - 1, size, _height - 1, color_fg);
        drawLine(size, _height - size - 1, 0, _height - 1, color_fg);
        break;
      case 2:  // up right
        drawLine(_width - size - 1, 0, _width - 1, 0, color_fg);
        drawLine(_width - size - 1, size, _width - 1, 0, color_fg);
        drawLine(_width - 1, size, _width - 1, 0, color_fg);
        break;
      case 3:  // bot right
        drawLine(_width - size - 1, _height - size - 1, _width - 1, _height - 1, color_fg);
        drawLine(_width - 1, _height - 1 - size, _width - 1, _height - 1, color_fg);
        drawLine(_width - 1 - size, _height - 1, _width - 1, _height - 1, color_fg);
        break;
    }

    // user has to get the chance to release
    if (i > 0)
      delay(1000);

    for (uint8_t j = 0; j < 8; j++) {
      // Use a lower detect threshold as corners tend to be less sensitive
      while (!validTouch(&x_tmp, &y_tmp, Z_THRESHOLD / 2))
        ;
      values[i * 2] += x_tmp;
      values[i * 2 + 1] += y_tmp;
    }
    values[i * 2] /= 8;
    values[i * 2 + 1] /= 8;
  }

  // from case 0 to case 1, the y value changed.
  // If the measured delta of the touch x axis is bigger than the delta of the y axis, the touch and TFT axes are switched.
  touchCalibration_rotate = false;
  if (abs(values[0] - values[2]) > abs(values[1] - values[3])) {
    touchCalibration_rotate = true;
    touchCalibration_x0 = (values[1] + values[3]) / 2;  // calc min x
    touchCalibration_x1 = (values[5] + values[7]) / 2;  // calc max x
    touchCalibration_y0 = (values[0] + values[4]) / 2;  // calc min y
    touchCalibration_y1 = (values[2] + values[6]) / 2;  // calc max y
  } else {
    touchCalibration_x0 = (values[0] + values[2]) / 2;  // calc min x
    touchCalibration_x1 = (values[4] + values[6]) / 2;  // calc max x
    touchCalibration_y0 = (values[1] + values[5]) / 2;  // calc min y
    touchCalibration_y1 = (values[3] + values[7]) / 2;  // calc max y
  }

  // in addition, the touch screen axis could be in the opposite direction of the TFT axis
  touchCalibration_invert_x = false;
  if (touchCalibration_x0 > touchCalibration_x1) {
    values[0] = touchCalibration_x0;
    touchCalibration_x0 = touchCalibration_x1;
    touchCalibration_x1 = values[0];
    touchCalibration_invert_x = true;
  }
  touchCalibration_invert_y = false;
  if (touchCalibration_y0 > touchCalibration_y1) {
    values[0] = touchCalibration_y0;
    touchCalibration_y0 = touchCalibration_y1;
    touchCalibration_y1 = values[0];
    touchCalibration_invert_y = true;
  }

  // pre calculate
  touchCalibration_x1 -= touchCalibration_x0;
  touchCalibration_y1 -= touchCalibration_y0;

  if (touchCalibration_x0 == 0)
    touchCalibration_x0 = 1;
  if (touchCalibration_x1 == 0)
    touchCalibration_x1 = 1;
  if (touchCalibration_y0 == 0)
    touchCalibration_y0 = 1;
  if (touchCalibration_y1 == 0)
    touchCalibration_y1 = 1;

  // export parameters, if pointer valid
  if (parameters != NULL) {
    parameters[0] = touchCalibration_x0;
    parameters[1] = touchCalibration_x1;
    parameters[2] = touchCalibration_y0;
    parameters[3] = touchCalibration_y1;
    parameters[4] = touchCalibration_rotate | (touchCalibration_invert_x << 1) | (touchCalibration_invert_y << 2);
  }
}

/***************************************************************************************
** Function name:           setTouch
** Description:             imports calibration parameters for touchscreen.
***************************************************************************************/
void TFT_eSPI::setTouch(uint16_t* parameters) {
  touchCalibration_x0 = parameters[0];
  touchCalibration_x1 = parameters[1];
  touchCalibration_y0 = parameters[2];
  touchCalibration_y1 = parameters[3];

  if (touchCalibration_x0 == 0)
    touchCalibration_x0 = 1;
  if (touchCalibration_x1 == 0)
    touchCalibration_x1 = 1;
  if (touchCalibration_y0 == 0)
    touchCalibration_y0 = 1;
  if (touchCalibration_y1 == 0)
    touchCalibration_y1 = 1;

  touchCalibration_rotate = parameters[4] & 0x01;
  touchCalibration_invert_x = parameters[4] & 0x02;
  touchCalibration_invert_y = parameters[4] & 0x04;
}
